window.nova = {};
(function() {
    var windowWidth = 0;
    var windowHeight = 0;
    var lastWindowHeight = 0;

    nova.application = {
        histories: new Array(),
        currentPage: null,
        settings: {            
            
        },
        isTouchable: false,
        isIOS: false,
        start: function(url) {
            var obj = this;
            obj.events.backButton(function(e) {
                if (obj.currentPage.isBackbuttonDisabled) {
                    return;
                }
                if (obj.currentPage.backbuttonHandlers.length == 0) {
                    obj.goBack();
                } else {
                    var handler = obj.currentPage.backbuttonHandlers.pop();
                    handler();
                }
            });
            obj.events.onOrientationChanged(function(width, height) {
                if (obj.currentPage && obj.currentPage.onOrientationChanged) {
                    obj.currentPage.onOrientationChanged(width, height);
                }
            });

            console.log("obj started from: " + url);
            $("#body").height($(window).height() + "px");
            initPlatformInformation();

            windowWidth = null;
            windowHeight = $(document).height();
            lastWindowHeight = windowHeight;

            obj.gotoPage(url);
        },
        gotoPage: function(pageOrUrl) {
            var obj = this;
            var page = null;

            if (pageOrUrl instanceof nova.Page) {
                page = pageOrUrl;
            } else {
                page = new nova.Page(pageOrUrl);
            }

            if (obj.currentPage != null) {
                obj.currentPage.onNavigatingAway(function() {
                    if (obj.currentPage.needAddingToHistory) {
                        obj.histories.push(obj.currentPage.clone());
                    }
                    loadNewPage();
                });
            } else {
                loadNewPage();
            }

            function loadNewPage() {
                page.onViewLoaded(function() {
                    var oldPage = obj.currentPage;
                    obj.currentPage = page;
                    obj.onPageChanged(oldPage, obj.currentPage);
                });
                page.load();
            }
        },
        onPageChanged: function(oldPage, newPage) {

        },
        goBack: function(urlUntil) {
            var obj = this;
            if (obj.histories.length == 0) {
                if (obj.isIOS == false) {
                    navigator.notification.confirm("Are you sure you want to exit the app?", function(btnNumber) {
                        if (btnNumber == 1) {
                            nova.application.exit();
                        }
                    }, 'Confirm', 'Exit,Cancel');
                }
                return;
            }
            var page = this.histories.pop();
            if (urlUntil != undefined) {
                while (page.url != urlUntil && this.histories.length > 0) {
                    page = this.histories.pop();
                }
            }
            obj.currentPage.onNavigatingAway(function() {
                var previousPage = obj.currentPage;
                obj.currentPage = page;
                obj.currentPage.onViewLoaded(function() {
                    obj.onPageChanged(previousPage, page);
                });
                obj.currentPage.load();
            });
        },
        exit: function() {
            navigator.app.exitApp();
        },
        isOverflowScrollSupported: function() {
            return this.isTouchable == false;
        },
        events: {
            deviceReady: function(callback) {
                document.addEventListener("deviceready", callback, false);
            },
            backButton: function(callback) {
                document.addEventListener("backbutton", callback, false);
            },
            menuButton: function(callback) {
                document.addEventListener("menubutton", callback, false);
            },
            pause: function(callback) {
                document.addEventListener("pause", callback, false);
            },
            resume: function(callback) {
                document.addEventListener("resume", callback, false);
            },
            searchButton: function(callback) {
                document.addEventListener("searchbutton", callback, false);
            },
            online: function(callback) {
                document.addEventListener("online", callback, false);
            },
            offline: function(callback) {
                document.addEventListener("offline", callback, false);
            },
            batteryCritical: function(callback) {
                document.addEventListener("batterycritical", callback, false);
            },
            batteryLow: function(callback) {
                document.addEventListener("batterylow", callback, false);
            },
            batteryStatus: function(callback) {
                document.addEventListener("batterystatus", callback, false);
            },
            startCallButton: function(callback) {
                document.addEventListener("startcallbutton", callback, false);
            },
            endCallButton: function(callback) {
                document.addEventListener("endcallbutton", callback, false);
            },
            volumeDownButton: function(callback) {
                document.addEventListener("volumedownbutton", callback, false);
            },
            volumeUpButton: function(callback) {
                document.addEventListener("volumeupbutton", callback, false);
            },
            onOrientationChanged: function(callback) {
                $(window).bind('orientationchange', function() {
                    if (windowHeight == lastWindowHeight) {
                        calculateWindowWidth(function() {
                            callback(windowHeight, windowWidth);
                            lastWindowHeight = windowWidth;
                        });
                    } else {
                        calculateWindowWidth(function() {
                            callback(windowWidth, windowHeight);
                            lastWindowHeight = windowHeight;
                        });
                    }
                });
            }
        }
    };

    // private methods -----------------------------------
    
    function initPlatformInformation() {
        var iosDevices = ['iphone', 'ipad', 'ipod'];
        var touchDevices = ['android', 'phone', 'blackbery'].concat(iosDevices);
        var appVersion = navigator.appVersion.toLowerCase();
        for (var i = 0; i < touchDevices.length; i++) {
            if (appVersion.indexOf(touchDevices[i]) > -1) {
                nova.application.isTouchable = true;
                break;
            }
        }
        for (var j = 0; j < iosDevices.length; j++) {
            if (appVersion.indexOf(iosDevices[j]) > -1) {
                nova.application.isIOS = true;
                break;
            }
        }
    }
    
    function calculateWindowWidth(widthCallback) {
        if (windowWidth != null) {
            widthCallback();
            return;
        }
        var counter = 0;
        var timer = setInterval(function () {
            if (counter >= 50) {
                windowWidth = windowHeight;
                clearInterval(timer);
                widthCallback();
                return;
            }
            var height = $(window).height();
            if (height != windowHeight) {
                if (height == windowWidth) {
                    clearInterval(timer);
                    widthCallback();
                } else {
                    windowWidth = height;
                }
            }
            counter++;
        }, 50);
    }

})();



(function() {
    nova.touch = {
        eventNames: {
            touchstart: "touchstart",
            touchmove: "touchmove",
            touchend: "touchend",
            touchcancel: "touchcancel"
        },
        bindClick: function(selector, func) {
            if (nova.application.isTouchable == false) {
                $(selector).click(function(e) {
                    func.call(this, e);
                });
                return;
            }
            var isMoving = false;
            var startTime = null;
            $(selector).bind(this.eventNames.touchstart, function(e) {
                isMoving = false;
                startTime = new Date();
                $(this).addClass("touching");
            });
            $(selector).bind(this.eventNames.touchmove, function(e) {
                isMoving = true;
            });
            $(selector).bind(this.eventNames.touchend, function(e) {
                var $me = $(this);
                $me.removeClass("touching");
                var duration = new Date() - startTime;
                if (!isMoving && duration < 1000) {
                    $me.addClass("clicking");
                    func.call(this, e);
                    setTimeout(function() {
                        $me.removeClass("clicking");
                    }, 500);
                }

            });
        },
        getTouchPosition: function(event) {
            try {
                var touch = event.targetTouches[0];
                if (touch == undefined) {
                    return null;
                }
                return {
                    x: touch.pageX,
                    y: touch.pageY
                };
            } catch(ex) {
                alert(ex);
            }
        }
    };
})();
(function() {
    nova.ScrollerBase = function (selector) {
        this.selector = selector;
        this.direction = nova.ScrollerBase.directions.vertical;
        this.horizontalPositions = [];
        this.verticalPositions = [];
        this.hasVerticalParent = false;
        this.hasHorizontalParent = false;
        this.isBounceable = true;
        this.bounceRate = 0.5;
        this.formFields = "input,select,textarea";

        this._$scroller = null;
        this._$content = null;
        this._scrollerHeight = 0;
        this._scrollerWidth = 0;
        this._lastTouchPosition = null;
        this._startDirection = null;
        this._isScrollingCancelled = false;
        this._lastVerticalDistance = 0;
        this._lastHorizontalDistance = 0;
    };

    nova.ScrollerBase.directions = {
        horizontal: "horizontal",
        vertical: "vertical"
    };

    nova.ScrollerBase.prototype.init = function () {
        var obj = this;
        obj._$scroller = $(obj.selector);
        obj._$scroller.addClass('scroller');
        if (nova.application.isOverflowScrollSupported()) {
            var scrollAttr = 'overflow-y';
            if (obj.direction == nova.ScrollerBase.directions.horizontal) {
                scrollAttr = 'overflow-x';
            }
            obj._$scroller.css(scrollAttr, 'scroll');
            return;
        }
        obj._$content = obj._$scroller.children('.scroller-content');
        if (obj._$content.length == 0) {
            obj._$scroller.wrapInner('<div class="scroller-content"></div>');
            obj._$content = obj._$scroller.children();
        }
        var events = nova.touch.eventNames;
        obj._$scroller[0].addEventListener(events.touchstart, function (e) {
            e.preventDefault();
            obj._handleTouchstart(e);
        });
        obj._$scroller[0].addEventListener(events.touchmove, function (e) {
            obj._handleTouchmove(e);
        });
        obj._$scroller[0].addEventListener(events.touchend, function (e) {
            obj._handleTouchend(e);
        });

        var $fields = obj._$scroller.find(obj.formFields);
        if ($fields.length > 0) {
            $fields.each(function () {
                this.addEventListener(events.touchstart, function (e) {
                    e.stopPropagation();
                });
                this.addEventListener(events.touchmove, function (e) {
                    e.stopPropagation();
                });
                this.addEventListener(events.touchend, function (e) {
                    e.stopPropagation();
                });
            });
        }
    };

    nova.ScrollerBase.prototype.isHorizontal = function () {
        return this.direction == nova.ScrollerBase.directions.horizontal;
    };

    nova.ScrollerBase.prototype.isVertical = function () {
        return this.direction == nova.ScrollerBase.directions.vertical;
    };

    nova.ScrollerBase.prototype.setContentPosition = function (position) {
        var p = position || { left: 0, top: 0 };
        this._$content.css("left", p.x + "px").css("top", p.y + "px");
    };

    nova.ScrollerBase.prototype.scrollTo = function (top, left) {
        if (this._$content == null) {
            return;
        }
        var paras = {};
        if (top != null) {
            paras.top = top;
        }
        if (left != null) {
            paras.left = left;
        }
        this._$content.animate(paras, 'fast');
    };

    nova.ScrollerBase.prototype._handleTouchstart = function (e) {
        var obj = this;
        obj._scrollerHeight = obj._$scroller.height();
        obj._scrollerWidth = obj._$scroller.width();
        obj._lastTouchPosition = nova.touch.getTouchPosition(e);
        obj._startDirection = null;
        obj._isScrollingCancelled = false;
        obj._lastVerticalDistance = 0;
        obj._lastHorizontalDistance = 0;
    };

    nova.ScrollerBase.prototype._handleTouchmove = function (e) {
        var obj = this;
        if (obj._isScrollingCancelled) {
            return;
        }
        if (obj._hasSameDirectionParent()) {
            e.stopPropagation();
        }
        var position = nova.touch.getTouchPosition(e);
        obj._setStartDirection(position);
        obj._setArgsCancelledOrStopped(e);
        if (obj._isScrollingCancelled) {
            return;
        }
        var contentPosition = obj._getNextContentPosition(position);
        obj.setContentPosition(contentPosition);
        obj._lastTouchPosition = position;
    };

    nova.ScrollerBase.prototype._handleTouchend = function (e) {
        this._lastVerticalDistance *= 10;
        this._lastHorizontalDistance *= 10;
        this._slowDown();
    };

    nova.ScrollerBase.prototype._getContentPosition = function () {
        var contentPosition = this._$content.position();
        return { x: contentPosition.left, y: contentPosition.top };
    };

    nova.ScrollerBase.prototype._slowDown = function () {
        var obj = this;
        var contentPosition = this._getContentPosition();
        var nextPosition = this._getSlowDownPosition(contentPosition);
        this.setContentPosition(nextPosition);
        if (this._lastVerticalDistance != 0 || this._lastHorizontalDistance != 0) {
            setTimeout(function () {
                obj._slowDown();
            }, 20);
        }
    };

    nova.ScrollerBase.prototype._hasSameDirectionParent = function () {
        return (this.hasVerticalParent && this.direction == nova.ScrollerBase.directions.vertical)
            || (this.hasHorizontalParent && this.direction == nova.ScrollerBase.directions.horizontal);
    };

    nova.ScrollerBase.prototype._setStartDirection = function (position) {
        var obj = this;
        if (obj._startDirection == null) {
            if (obj._lastTouchPosition != null) {
                var isVerticalScrolling = Math.abs(position.y - obj._lastTouchPosition.y)
                    > Math.abs(position.x - obj._lastTouchPosition.x);
                obj._startDirection = isVerticalScrolling ? nova.ScrollerBase.directions.vertical : nova.ScrollerBase.directions.horizontal;
            }
        }
    };

    nova.ScrollerBase.prototype._setArgsCancelledOrStopped = function (e) {
        var obj = this;
        if (obj.hasHorizontalParent && obj.isVertical() && obj._startDirection == nova.ScrollerBase.directions.horizontal) {
            obj._isScrollingCancelled = true;
            return;
        }
        if (obj.hasVerticalParent && obj.isHorizontal() && obj._startDirection == nova.ScrollerBase.directions.vertical) {
            obj._isScrollingCancelled = true;
            return;
        }
        if (obj.hasHorizontalParent && obj.isVertical() && obj._startDirection == nova.ScrollerBase.directions.vertical) {
            e.stopPropagation();
        }
        if (obj.hasVerticalParent && obj.isHorizontal() && obj._startDirection == nova.ScrollerBase.directions.horizontal) {
            e.stopPropagation();
        }
    };

    nova.ScrollerBase.prototype._getBoundary = function () {
        var boundary = {
            topLeft: {
                x: 0,
                y: 0
            },
            bottomRight: {
                x: 0,
                y: 0
            }
        };
        if (this.isVertical()) {
            boundary.topLeft.y = this._$scroller.height() - this._$content.height();
        }
        if (this.isHorizontal()) {
            boundary.topLeft.x = this._$scroller.width() - this._$content.width();
        }
        if (boundary.topLeft.y > 0) {
            boundary.topLeft.y = 0;
        }
        if (boundary.topLeft.x > 0) {
            boundary.topLeft.x = 0;
        }
        return boundary;
    };

    nova.ScrollerBase.prototype._adjustPositionToBoundary = function (nextPosition, contentPosition, boundary) {
        var obj = this;
        if (obj.isHorizontal()) {
            if (nextPosition.x > boundary.bottomRight.x || nextPosition.x < boundary.topLeft.x) {
                if (obj.isBounceable) {
                    nextPosition.x = contentPosition.x + (nextPosition.x - contentPosition.x) * obj.bounceRate;
                } else {
                    nextPosition.x = contentPosition.x;
                }
            }
        }
        if (obj.isVertical()) {
            if (nextPosition.y > boundary.bottomRight.y || nextPosition.y < boundary.topLeft.y) {
                if (obj.isBounceable) {
                    nextPosition.y = contentPosition.y + (nextPosition.y - contentPosition.y) * obj.bounceRate;
                } else {
                    nextPosition.y = contentPosition.y;
                }
            }
        }
    };

    nova.ScrollerBase.prototype._getSlowDownPosition = function (contentPosition) {
        var boundary = this._getBoundary();
        var minDistance = 6;
        var nextPosition = {
            x: this.isHorizontal() ? contentPosition.x + this._lastHorizontalDistance * this.bounceRate : contentPosition.x,
            y: this.isVertical() ? contentPosition.y + this._lastVerticalDistance * this.bounceRate : contentPosition.y
        };
        if (this.isVertical()) {
            if (nextPosition.y < boundary.topLeft.y) {
                nextPosition.y = boundary.topLeft.y + (contentPosition.y - boundary.topLeft.y) * this.bounceRate;
                if (Math.abs(nextPosition.y - boundary.topLeft.y) < minDistance) {
                    nextPosition.y = boundary.topLeft.y;
                    this._lastVerticalDistance = 0;
                } else {
                    this._lastVerticalDistance = nextPosition.y - contentPosition.y;
                }
            } else if (nextPosition.y > boundary.bottomRight.y) {
                nextPosition.y = boundary.bottomRight.y + (contentPosition.y - boundary.bottomRight.y) * this.bounceRate;
                if (Math.abs(nextPosition.y - boundary.bottomRight.y) < minDistance) {
                    nextPosition.y = boundary.bottomRight.y;
                    this._lastVerticalDistance = 0;
                } else {
                    this._lastVerticalDistance = nextPosition.y - contentPosition.y;
                }
            } else {
                if (Math.abs(nextPosition.y - contentPosition.y) < minDistance) {
                    this._lastVerticalDistance = 0;
                } else {
                    this._lastVerticalDistance = nextPosition.y - contentPosition.y;
                }
            }
        }
        if (this.isHorizontal()) {
            if (nextPosition.x < boundary.topLeft.x) {
                nextPosition.x = boundary.topLeft.x + (contentPosition.x - boundary.topLeft.x) * this.bounceRate;
                if (Math.abs(nextPosition.x - boundary.topLeft.x) < minDistance) {
                    nextPosition.x = boundary.topLeft.x;
                    this._lastHorizontalDistance = 0;
                } else {
                    this._lastHorizontalDistance = nextPosition.x - contentPosition.x;
                }
            } else if (nextPosition.x > boundary.bottomRight.x) {
                nextPosition.x = boundary.bottomRight.x + (contentPosition.x - boundary.bottomRight.x) * this.bounceRate;
                if (Math.abs(nextPosition.x - boundary.bottomRight.x) < minDistance) {
                    nextPosition.x = boundary.bottomRight.x;
                    this._lastHorizontalDistance = 0;
                } else {
                    this._lastHorizontalDistance = nextPosition.x - contentPosition.x;
                }
            } else {
                if (Math.abs(nextPosition.x - contentPosition.x) < minDistance) {
                    this._lastHorizontalDistance = 0;
                } else {
                    this._lastHorizontalDistance = nextPosition.x - contentPosition.x;
                }
            }
        }
        return nextPosition;
    };

    nova.ScrollerBase.prototype._getNextContentPosition = function (touchPosition) {
        var obj = this;
        var boundary = obj._getBoundary();
        var hDistance = touchPosition.x - obj._lastTouchPosition.x;
        var vDistance = touchPosition.y - obj._lastTouchPosition.y;
        var contentPosition = this._getContentPosition();
        var nextPosition = {
            x: obj.isHorizontal() ? contentPosition.x + hDistance : contentPosition.x,
            y: obj.isVertical() ? contentPosition.y + vDistance : contentPosition.y
        };
        obj._adjustPositionToBoundary(nextPosition, contentPosition, boundary);
        obj._lastVerticalDistance = nextPosition.y - contentPosition.y;
        obj._lastHorizontalDistance = nextPosition.x - contentPosition.x;
        return nextPosition;
    };
})();



(function() {
    nova.Scroller = function (selector) {
        nova.ScrollerBase.call(this, selector);
    };

    nova.Scroller.prototype = new nova.ScrollerBase();
    nova.Scroller.constructor = nova.Scroller;
})();
(function() {
    nova.View = function (url, wrapper) {
        this.url = url;
        this.wrapper = wrapper == undefined ? "#body" : wrapper;
    };

    nova.View.prototype.onLoaded = null;

    nova.View.prototype.load = function () {
        var obj = this;
        $.ajax({
            url: obj.url + "?timestamp=" + new Date().getTime(),
            dataType: "html",
            type: "get",
            success: function (html) {
                try {
                    setTimeout(function () { });//this is used to fix android 4.1 querks
                    $(obj.wrapper).html(html);
                    $(obj).trigger("onViewLoaded");
                    obj.onLoaded && obj.onLoaded();
                } catch (ex) {
                    alert('loading view: "' + obj.url + '" error: ' + ex);
                }
            },
            error: function (err) {
                var errorEvt = $.Event("onLoadingError");
                errorEvt.data = err;
                $(obj).trigger(errorEvt);
            }
        });
    };

    nova.View.prototype.GetHtml = function () {
        return $(this.wrapper).html();
    };

    nova.View.prototype.onViewLoaded = function (callback) {
        $(this).bind("onViewLoaded", callback);

    };

    nova.View.prototype.onLoadingError = function (callback) {
        $(this).bind("onLoadingError", callback);
    };
})();



(function() {
    nova.Page = function (url, wrapper) {
        nova.View.call(this, url, wrapper);
        this.needAddingToHistory = true;
        this.historyUrl = null;
        this.backbuttonHandlers = [];
        this.isBackbuttonDisabled = false;
    };

    nova.Page.prototype = new nova.View();
    nova.Page.constructor = nova.Page;

    nova.Page.prototype.onLoaded = function() {
        this.calculateFixedSize($(window).width(), $(window).height());
    };

    nova.Page.prototype.clone = function () {
        var page = new nova.Page(this.url, this.wrapper);
        for (var p in this) {
            var value = this[p];
            if (p.indexOf("jQuery") != 0) {
                page[p] = value;
            }
        }
        page.backbuttonHandlers = [];
        return page;
    };

    nova.Page.prototype.backbutton = function (func) {
        this.backbuttonHandlers.push(func);
    };

    nova.Page.prototype.autoCancelDialog = function (dialog) {
        this.backbutton(function () {
            dialog.close();
        });
    };

    nova.Page.prototype.calculateFixedSize = function (width, height) {

    };

    nova.Page.prototype.onOrientationChanged = function (width, height) {
        $('#body').height(height);
        this.calculateFixedSize(width, height);
    };

    nova.Page.prototype.onNavigatingAway = function (continueCallback) {
        continueCallback();
    };
})();


(function() {
    nova.Form = function (selector) {
        this.selector = selector;
    };

    nova.Form.prototype.init = function () {
        var obj = this;
        $(obj.selector).each(function () {
            nova.touch.bindClick($(this).find(".form-text"), function () {
                obj._initInput(this, "text");
            });

            $(this).find(".form-password").each(function () {
                var $this = $(this);
                var value = $this.html();
                $this.attr("data-value", value);
                $this.html("");
                for (var i = 0; i < value.length; i++) {
                    $this.append("*");
                }
            });
            nova.touch.bindClick($(this).find(".form-password"), function () {
                obj._initInput(this, "password");
            });
            nova.touch.bindClick($(this).find(".form-checkbox"), function () {
                obj.initCheckbox(this);
            });
        });
    };

    nova.Form.prototype.initCheckbox = function (input) {
        var $check = $(input);
        if ($check.hasClass("checked")) {
            $check.removeClass("checked");
        }
        else {
            $check.addClass("checked");
        }
    };

    nova.Form.prototype._initInput = function (input, type) {
        var $text = $(input);
        var html = "<div class='form-modal'><div class='form-modal-w1'>";
        html += "<div class='form-text-w1'>";
        switch (type) {
            case "text":
                html += "<input class='pop-input' type='text' value='" + $text.html() + "' />";
                break;
            case "password":
                html += "<input class='pop-input' type='password' value='" + $text.attr("data-value") + "' />";
                break;
            case "textarea":
                html += "<textarea class='pop-input'>" + $text.html() + "</textarea>";
                break;
        }
        html += "</div>";
        html += "<div class='group'>";
        html += "<div class='btn btn-ok'><span>OK</span></div>";
        html += "<div class='btn btn-cancel'><span>Cancel</span></div>";
        html += "</div>";
        html += "</div></div>";
        var $html = $(html);
        $html.appendTo("#body");
        var $textInput = $html.find(".pop-input");
        $textInput.focus();
        nova.touch.bindClick($html.find(".btn-ok"), function () {
            var value = $html.find(".pop-input").val();
            if (type == "password") {
                $text.html("");
                for (var i = 0; i < value.length; i++) {
                    $text.append("*");
                }
                $text.attr("data-value", value);
            }
            else {
                $text.html(value);
            }
            $html.remove();
        });
        nova.touch.bindClick($html.find(".btn-cancel"), function () {
            $html.remove();
        });
    };
})();

(function() {
    nova.data = {};
})();
Array.prototype.each = function (func) {
    for (var i = 0; i < this.length; i++) {
        var item = this[i];
        var result = func.call(item, i, item);
        if (result == false) {
            return;
        }
    }
};

Array.prototype.sum = function(propertyOrFunc) {
    var total = 0;
    var isFunc = typeof(propertyOrFunc) == "function";
    this.each(function() {
        if (isFunc) {
            total += propertyOrFunc.call(this);
        } else {
            var value = this[propertyOrFunc];
            if (value != undefined && value != null) {
                total += value * 1;
            }
        }
    });
    return total;
};

Array.prototype.where = function (predicateFunction) {
    var results = new Array();
    this.each(function() {
        if (predicateFunction.call(this)) {
            results.push(this);
        }
    });
    return results;
};

Array.prototype.orderBy = function (property, compare) {
    var items = this;
    for (var i = 0; i < items.length - 1; i++) {
        for (var j = 0; j < items.length - 1 - i; j++) {
            if (isFirstGreaterThanSecond(items[j], items[j + 1])) {
                var temp = items[j + 1];
                items[j + 1] = items[j];
                items[j] = temp;
            }
        }
    }
    function isFirstGreaterThanSecond(first, second) {
        if (compare != undefined) {
            return compare(first, second);
        }
        else if (property == undefined || property == null) {
            return first > second;
        }
        else {
            return first[property] > second[property];
        }
    }

    return items;
};

Array.prototype.orderByDescending = function (property, compare) {
    var items = this;
    for (var i = 0; i < items.length - 1; i++) {
        for (var j = 0; j < items.length - 1 - i; j++) {
            if (!isFirstGreaterThanSecond(items[j], items[j + 1])) {
                var temp = items[j + 1];
                items[j + 1] = items[j];
                items[j] = temp;
            }
        }
    }
    function isFirstGreaterThanSecond(first, second) {
        if (compare != undefined) {
            return compare(first, second);
        }
        else if (property == undefined || property == null) {
            return first > second;
        }
        else {
            return first[property] > second[property];
        }
    }
    return items;
};

Array.prototype.groupBy = function (predicate) {
    var results = [];
    var items = this;

    var keys = {}, index = 0;
    for (var i = 0; i < items.length; i++) {
        var selector;
        if (typeof predicate === "string") {
            selector = items[i][predicate];
        } else {
            selector = predicate(items[i]);
        }
        if (keys[selector] === undefined) {
            keys[selector] = index++;
            results.push({ key: selector, value: [items[i]] });
        } else {
            results[keys[selector]].value.push(items[i]);
        }
    }
    return results;
};

Array.prototype.skip = function (count) {
    var items = new Array();
    for (var i = count; i < this.length; i++) {
        items.push(this[i]);
    }
    return items;
};

Array.prototype.take = function (count) {
    var items = new Array();
    for (var i = 0; i < this.length && i < count; i++) {
        items.push(this[i]);
    }
    return items;
};

Array.prototype.firstOrDefault = function (predicateFunction) {
    if (this.length == 0) {
        return null;
    }
    if (predicateFunction == undefined || predicateFunction == null) {
        return this[0];
    }
    var item = null;
    this.each(function () {
        if (predicateFunction.call(this)) {
            item = this;
            return false;
        }
    });
    return item;
};

Array.prototype.any = function(predicateFunction) {
    if (predicateFunction == undefined || predicateFunction == null) {
        return this.length > 0;
    }
    var hasAny = false;
    this.each(function() {
        if (predicateFunction.call(this)) {
            hasAny = true;
            return false;
        }
    });
    return hasAny;
};

Array.prototype.select = function (predicateFunction) {
    if (predicateFunction == undefined || predicateFunction == null) {
        throw "parameter predicateFunction cannot be null or undefined";
    }
    var items = [];
    this.each(function () {
        items.push(predicateFunction.call(this));
    });
    return items;
};

(function() {
    nova.data.Entity = function() {
        this.id = 0;
    };

    var dataTypes = {
        integer: "integer",
        decimal: "decimal",
        string: "string",
        bool: "boolean",
        object: "object",
        date: "date"
    };

    nova.data.Entity.dataTypes = dataTypes;

    nova.data.Entity.prototype = {
        getFields: function() {
            var fields = [];
            var instance = this;
            for (var property in instance) {
                var type = typeof(instance[property]);
                var field = {
                    name: property
                };
                switch (type) {
                case "number":
                    if (instance[field] % 1 != 0) {
                        field.type = dataTypes.decimal;
                    } else {
                        field.type = dataTypes.integer;
                    }
                    break;
                case "string":
                    field.type = dataTypes.string;
                    break;
                case "boolean":
                    field.type = dataTypes.bool;
                    break;
                case "object":
                    var value = instance[field.name];
                    if (value instanceof Date) {
                        field.type = dataTypes.date;
                    }
                    break;
                default:
                    break;
                }
                if (field.type != undefined) {
                    fields.push(field);
                }
            }
            return fields;
        }
    };


    nova.data.Entity.getDbType = function(type) {
        switch (type) {
            case dataTypes.integer:
            case dataTypes.bool:
            case dataTypes.date:
                return "INTEGER";
            case dataTypes.decimal:
                return "NUMERIC";
            case dataTypes.string:
                return "TEXT";
            default:
                break;
            }
            return "NULL";
    };

    nova.data.Entity.parseFromDbValue = function(type, value) {
        if (value == null) {
            return null;
        }
        switch (type) {
            case dataTypes.integer:
            case dataTypes.decimal:
            case dataTypes.string:
                return value;
            case dataTypes.bool:
                return value == 1 ? true : false;
            case dataTypes.date:
                return new Date(value);
            default:
                break;
            }
        return value.toString();
    };

    nova.data.Entity.getDbValue = function(type, value) {
        if (value == null) {
            return "null";
        }
        switch (type) {
            case dataTypes.integer:
            case dataTypes.decimal:
                return value;
            case dataTypes.string:
                var reg = new RegExp("'", "g");
                return "'" + (value + '').replace(reg, "''") + "'";
            case dataTypes.bool:
                return value ? 1 : 0;
            case dataTypes.date:
                if (typeof(value) == "string") {
                    return eval("new " + value.replace(/\//g, '')).getTime();
                }
                return value.getTime();
        default:
            break;
        }
        return value.toString();
    };
})();

(function() {
    nova.data.Queryable = function(repository, expression) {
        this.repository = repository;
        this._wheresCount = 0;
        this.sql = '';
        if (expression != undefined) {
            this.where(expression);
        }
    };

    nova.data.Queryable.prototype = {
        where: function(expression) {
            if (this.sql == '') {
                this.sql = 'select * from ' + this.repository.table + ' where ' + expression;
            } else {
                this.sql = 'select * from (' + this.sql + ') as t' + this._wheresCount.length + ' where ' + expression;
            }
            this._wheresCount++;
            return this;
        },
        orderBy: function(expression) {
            if (this.sql == '') {
                this.sql = 'select * from ' + this.repository.table + ' order by ' + expression;
            } else {
                this.sql += ' order by ' + expression;
            }
            return this;
        },
        thenBy: function(expression) {
            if (this.sql == '') {
                this.orderBy(expression);
            } else {
                this.sql += ' then by ' + expression;
            }
            return this;
        },
        firstOrDefault: function(callback, expression) {
            if (expression != undefined) {
                this.where(expression);
            }
            var sql = this.sql;
            if (sql == '') {
                sql = 'select * from ' + this.repository.table + ' limit 0,1';
            } else {
                var randomText = Math.abs(Math.round(Math.random() * 10000));
                sql = 'select * from (' + sql + ') as t' + randomText + ' limit 0,1';
            }
            this.query(sql, function(items) {
                if (items.length == 0) {
                    callback(null);
                } else {
                    callback(items[0]);
                }
            });
        },
        toArray: function(callback) {
            var sql = this.sql;
            if (sql == '') {
                sql = 'select * from ' + this.repository.table;
            }
            this.query(sql, callback);
        },
        query: function(sql, callback) {
            var repo = this.repository;
            repo.db.query(sql, function(items) {
                var fields = repo.getFields();
                var entities = [];
                items.each(function() {
                    var item = this;
                    var entity = new repo.type();
                    fields.each(function() {
                        entity[this.name] = nova.data.Entity.parseFromDbValue(this.type, item[this.name]);
                    });
                    entities.push(entity);
                });
                callback(entities);
            });
        }
    };
})();

(function() {
    nova.data.Repository = function(db, type, table) {
        this.db = db;
        this.type = type;
        this.table = table;
        this.pendingAddEntities = [];
        this.pendingDeleteEntities = [];
        this.pendingUpdateEntities = [];
    };

    nova.data.Repository.prototype = {
        toArray: function(callback) {
            var query = new nova.data.Queryable(this);
            query.toArray(callback);
        },
        add: function(entity) {
            this.pendingAddEntities.push(entity);
        },
        remove: function(entity) {
            this.pendingDeleteEntities.push(entity);
        },
        removeByWhere: function(expression, successCallback, errorCallback) {
            var where = expression == null || expression == '' ? '' : ' where ' + expression;
            this.db.executeSql('delete from ' + this.table + where, successCallback, errorCallback);
        },
        removeAll: function(successCallback, errorCallback) {
            this.removeByWhere('', successCallback, errorCallback);
        },
        update: function(entity) {
            this.pendingUpdateEntities.push(entity);
        },
        where: function(expression) {
            return new nova.data.Queryable(this, expression);
        },
        orderBy: function(expression) {
            return new nova.data.Queryable(this).orderBy(expression);
        },
        firstOrDefault: function(callback, expression) {
            return new nova.data.Queryable(this).firstOrDefault(callback, expression);
        },
        thenBy: function(expression) {
            return new nova.data.Queryable(this).thenBy(expression);
        },
        get: function(id, callback) {
            var query = new nova.data.Queryable(this, "id=" + id);
            query.toArray(function(entities) {
                callback(entities.firstOrDefault());
            });
        },
        getFields: function() {
            var instance = new this.type();
            return instance.getFields();
        }
    };
})();

(function() {
    var systemTables = {
        info: "__WebKitDatabaseInfoTable__",
        version: "versions",
        sequence: "sqlite_sequence",
        getAll:function() {
            return [this.info, this.version, this.sequence];
        }
    };

    nova.data.DbContext = function (name, version, displayName, estimatedSize) {
        this.db = null;
        this.logSqls = false;
        this.alertErrors = false;
        this.version = version;
        this.versions = new nova.data.Repository(this, nova.data.DbVersion, "versions");
        if (name != undefined) {
            if (window.openDatabase) {
                this.db = window.openDatabase(name, "1.0", displayName, estimatedSize);
            }
        }
    };

    nova.data.DbContext.prototype = {        
        init : function (callback) {
            var obj = this;
            obj.isTableExisting("versions", function (exists) {
                if (exists) {
                    obj.versions.toArray(function (entities) {
                        if (entities.length == 0) {
                            initVersionAndData(obj, callback);
                        } else {
                            var lastVersion = entities[0];
                            if (lastVersion.version != obj.version) {
                                var migrations = obj.getMigrations();
                                if (migrations == null || migrations.length == 0) {
                                    obj.reCreateTables(function() {
                                        initVersionAndData(obj, callback);
                                    }, null);
                                } else {
                                    doMigrations(obj, lastVersion.version, callback);
                                }
                            }
                            else {
                                if (callback != undefined && callback != null) {
                                    callback();
                                }
                            }
                        }
                    });
                } else {
                    obj.reCreateTables(function () {
                        initVersionAndData(obj, callback);
                    }, null);
                }
            });
        },
        getMigrations: function() {
            return [];
        },
        initData : function (callback) {
            callback && callback();
        },
        clearAllData : function (callback, excludeTables) {
            var obj = this;
            this.db.transaction(function (t) {
                t.executeSql("select name from sqlite_master where type=\"table\"", [],
                    function (t, r) {
                        var tables = [];
                        for (var x = 0; x < r.rows.length; x++) {
                            tables.push(r.rows.item(x).name);
                        }
                        if (!excludeTables) {
                            excludeTables = systemTables.getAll();
                        }

                        var dropSqls = [];
                        for (var di = 0; di < tables.length; di++) {
                            if (!excludeTables.any(function () {
                                return this == tables[di];
                            })) {
                                dropSqls.push("drop table " + tables[di]);
                            };
                        }
                        if (dropSqls.length == 0) {
                            callback && callback();
                            return;
                        }
                        obj.executeSql(dropSqls, function () {
                            callback && callback();
                        }, function () {
                            console.log('drop tables failed.');
                        });
                    },
                    function (t, e) {
                        console.log(e);
                    }
                );
            });
        },
        getTables : function () {
            var tables = [];
            for (property in this) {
                var query = this[property];
                if (query instanceof nova.data.Repository) {
                    tables.push(this[property].table);
                }
            }
            return tables;
        },
        isTableExisting : function (table, callback) {
            var obj = this;
            var sql = "SELECT name FROM sqlite_master WHERE type='table' AND name='" + table + "'";
            this.query(sql, function (items) {
                callback(items.length > 0);
            }, function (err) {
                if (obj.alertErrors) {
                    alert(sql + ":" + err);
                }
                return false;
            });
        },
        dropAllTables : function (callback) {
            var obj = this;
            var existingTablesQuery = "select name from sqlite_master where type='table'";
            obj.query(existingTablesQuery, function (tables) {
                var excluded = [systemTables.info, systemTables.sequence];
                var dropSqls = [];
                tables.each(function () {
                    var table = this;
                    var isExcluded = excluded.any(function () {
                        return this == table.name;
                    });
                    if (!isExcluded) {
                        dropSqls.push("DROP TABLE IF EXISTS " + table.name);
                    }
                });
                obj.executeSql(dropSqls, function () {
                    callback && callback();
                }, function () {
                    console.log('drop tables failed.');
                });
            });
        },
        reCreateTables : function (successCallback, errorCallback) {
            var obj = this;
            obj.dropAllTables(function () {
                var sqls = [];
                obj.getTables().each(function () {
                    var table = this;
                    var columns = [];
                    obj[table].getFields().each(function () {
                        if (this.name == "id") {
                            columns.push("id INTEGER PRIMARY KEY AUTOINCREMENT");
                        } else {
                            columns.push(this.name + " " + nova.data.Entity.getDbType(this.type));
                        }
                    });
                    sqls.push("CREATE TABLE " + table + " (" + columns.join() + ")");
                });
                obj.executeSql(sqls, successCallback, errorCallback);
            });
        },
        executeSql : function (sqls, successCallback, errorCallback) {
            var obj = this;
            if (this.db != null) {
                this.db.transaction(function (dbContext) {
                    if (sqls instanceof Array) {
                        for (var s = 0; s < sqls.length; s++) {
                            var sql = sqls[s];
                            if (obj.logSqls) {
                                console.log(sql);
                            }
                            dbContext.executeSql(sql);
                        }
                    } else {
                        if (obj.logSqls) {
                            console.log(sqls);
                        }
                        dbContext.executeSql(sqls);
                    }
                }, function (err) {
                    if (obj.alertErrors) {
                        alert(err);
                    }
                    if (errorCallback == undefined || errorCallback == null) {
                        throw err;
                    }
                }, function () {
                    if (successCallback != undefined) {
                        successCallback();
                    }
                });
            }
        },
        query : function (sql, successCallback, errorCallback, paras) {
            var obj = this;
            if (obj.db != null) {
                obj.db.transaction(function (dbctx) {
                    if (obj.logSqls) {
                        console.log(sql);
                    }
                    var sqlParas = paras == undefined ? [] : paras;
                    dbctx.executeSql(sql, sqlParas, function (tx, result) {
                        var items = [];
                        for (var i = 0; i < result.rows.length; i++) {
                            items.push(result.rows.item(i));
                        }
                        successCallback(items);
                    }, function (err) {
                        if (obj.alertErrors) {
                            alert(err);
                        }
                        if (errorCallback == undefined || errorCallback == null) {
                            throw err;
                        }
                        else {
                            errorCallback(err);
                        }
                    });
                }, function (err) {
                    if (obj.alertErrors) {
                        alert(err);
                    }
                    if (errorCallback == undefined || errorCallback == null) {
                        throw err;
                    }
                    else {
                        errorCallback(err);
                    }
                });
            }
        }
    };


    var initVersionAndData = function (obj, callback) {
        var version = new nova.data.DbVersion();
        version.version = obj.version;

        obj.versions.add(version);
        obj.saveChanges(function () {
            obj.initData(callback);
        }, null);
    };

    var doMigrations = function (obj, lastVersion, callback) {
        var index = 0;
        var migrations = obj.getMigrations();
        var version = obj.version;
        function exec() {
            if (migrations.length <= index) {
                updateVersion();
            } else {
                var migration = migrations[index];
                if (migration.version > lastVersion && migration.version <= version) {
                    migration.migrate(function () {
                        index++;
                        exec();
                    });
                } else {
                    index++;
                    exec();
                }
            }
        }
        
        function updateVersion() {
            var sql = "update versions set version = " + version;
            obj.executeSql(sql, callback, function(err) {
                console.log("run migration sqls error: " + err);
                if (obj.alertErrors) {
                    alert("run migration sqls error: " + err);
                }
            });
        }

        exec();
    };
})();


(function() {
    nova.data.DbContext.prototype.saveChanges = function(successCallback, errorCallback) {
        var obj = this;
        var sqlDelegates = [];
        var tables = this.getTables();
        for (var ti = 0; ti < tables.length; ti++) {
            var table = tables[ti];
            var query = this[table];
            if (query instanceof nova.data.Repository) {
                var fields = query.getFields();
                query.pendingDeleteEntities.each(function() {
                    var removeWhere = this;
                    if (this instanceof query.type) {
                        removeWhere = " where id=" + this.id;
                    }
                    var deleteSql = "delete from " + table + removeWhere;
                    sqlDelegates.push({
                        sql: deleteSql
                    });
                });

                query.pendingDeleteEntities = [];
                if (query.pendingAddEntities.any()) {
                    var columns = fields.select(function() {
                        return this.name;
                    }).join();

                    query.pendingAddEntities.each(function() {
                        var toAdd = this;
                        var values = [];
                        fields.each(function() {
                            if (this.name == "id") {
                                values.push("null");
                            } else {
                                values.push(nova.data.Entity.getDbValue(this.type, toAdd[this.name]));
                            }
                        });

                        var sqlInsert = "insert into " + table + " (" + columns + ") values (" + values.join() + ")";
                        sqlDelegates.push({
                            sql: sqlInsert,
                            entity: toAdd
                        });
                    });
                    query.pendingAddEntities = [];
                }

                query.pendingUpdateEntities.each(function() {
                    var toUpdate = this;
                    var sets = fields.where(function() {
                        return this.name != "id";
                    }).select(function() {
                        return this.name + "=" + nova.data.Entity.getDbValue(this.type, toUpdate[this.name]);
                    }).join();
                    var sqlUpdate = "update " + table + " set " + sets + " where id = " + toUpdate.id;
                    sqlDelegates.push({
                        sql: sqlUpdate
                    });
                });
                query.pendingUpdateEntities = [];
            }
        }
        if (this.db != null) {
            this.db.transaction(function(dbContext) {
                for (var s = 0; s < sqlDelegates.length; s++) {
                    var sqlDelegate = sqlDelegates[s];
                    if (obj.logSqls) {
                        console.log(sqlDelegate.sql);
                    }
                    dbContext.executeSql(sqlDelegate.sql, [], function(tx, result) {
                        if (sqlDelegate.entity) {
                            sqlDelegate.entity.id = result.insertId;
                        }
                    });
                }
            }, function(err) {
                if (obj.alertErrors) {
                    alert(err);
                }
                if (errorCallback == undefined || errorCallback == null) {
                    throw err;
                }
                errorCallback(err);
            }, function() {
                successCallback && successCallback();
            });
        }
    };
})();
(function() {
    nova.data.DbVersion = function() {
        nova.data.Entity.call(this);
        this.version = 0;
    };
    nova.data.DbVersion.prototype = new nova.data.Entity();
    nova.data.DbVersion.constructor = nova.data.DbVersion;
})();
nova.widgets = {};
(function() {
    var ns = nova.widgets;
    var directions = {
        horizontal: 'horizontal',
        vertical: 'vertical'
    };

    function onTouchMove(obj, xy, direction) {
        if (xy == null) {
            return;
        }
        if (obj._lastPosition == null) {
            obj._lastPosition = xy;
            return;
        }
        obj._vForce = 0;
        obj._hForce = 0;
        if (direction == directions.vertical) {
            obj._vForce = xy.y - obj._lastPosition.y;
        }
        else {
            obj._hForce = xy.x - obj._lastPosition.x;
        }
        var position = obj._$carousel.position();
        if (direction == directions.vertical) {
            obj._$carousel.css("top", (position.top + obj._vForce) + "px");
        } else {
            obj._$carousel.css("left", (position.left + obj._hForce) + "px");
        }
        obj._lastPosition = xy;
    }
    
    function getTargetIndex(obj, direction, sensitivity) {
        var index = obj._currentIndex;
        var prevMiddle = 0;
        var nextMiddle = 0;
        var count = 0;
        
        var position = obj._$carousel.position();
        if (direction == directions.vertical) {
            var newTop = position.top + obj._vForce * sensitivity;
            prevMiddle = -obj._height * obj._currentIndex - obj._height / 2;
            nextMiddle = prevMiddle + obj._height;
            if (newTop <= prevMiddle) {
                index = index - 1;
                if (index < 0) {
                    index = 0;
                }
            }
            if (newTop >= nextMiddle) {
                index = index + 1;
                count = obj._$carousel.children().length;
                if (index >= count) {
                    index = count - 1;
                }
            }
            return index;

        } else {
            var newLeft = position.left + obj._hForce * sensitivity;
            prevMiddle = -obj._width * obj._currentIndex - obj._width / 2;
            nextMiddle = prevMiddle + obj._width;
            if (newLeft <= prevMiddle) {
                index = index + 1;
                count = obj._$carousel.children().length;
                if (index >= count) {
                    index = count - 1;
                }
            }
            if (newLeft >= nextMiddle) {
                index = index - 1;
                if (index < 0) {
                    index = 0;
                }
            }
            return index;
        }
    }

    //=====================================================================================

    ns.Carousel = function(selector) {
        this.selector = selector;
        this.direction = directions.horizontal;
        this.duration = 400;
        this.loop = false;
        this.sensitivity = 10;
        
        this._$carousel = null;
        this._$wrapper = null;
        this._height = 0;
        this._width = 0;
        this._currentIndex = 0;
        this._lastPosition = null;
        this._vForce = 0;
        this._hForce = 0;
        this._isMoving = false;
    };

    ns.Carousel.prototype =
    {
        init: function() {
            var obj = this;
            obj._$carousel = $(obj.selector);
            obj._$wrapper = obj._$carousel.parent();
            obj._$wrapper.addClass("carousel-container");
            obj._$carousel.addClass("carousel");
            obj.refreshSize();

            var dom = obj._$carousel[0];
            var events = nova.touch.eventNames;
            dom.addEventListener(events.touchstart, function (e) {
                e.preventDefault();
                obj._isMoving = false;
                obj._$carousel.stop(true);
                obj._lastPosition = nova.touch.getTouchPosition(e);
            });
            dom.addEventListener(events.touchmove, function (e) {
                if (obj.direction == directions.vertical) {
                    if (e.scrollingVertical) {
                        return;
                    }
                    else {
                        e.scrollingVertical = true;
                    }
                }
                else{
                    if (e.scrollingHorizontal) {
                        return;
                    }
                    else {
                        e.scrollingHorizontal = true;
                    }
                }
                obj._isMoving = true;
                onTouchMove(obj, nova.touch.getTouchPosition(e), obj.direction);
            });
            dom.addEventListener(events.touchend, function () {
                if (obj._isMoving) {
                    var targetIndex = getTargetIndex(obj.direction, obj.sensitivity);
                    obj.gotoIndex(targetIndex);
                }
            });

        },
        getIndex:function() {
            return obj._currentIndex;
        },
        gotoIndex:function(index) {
            var obj = this;
            if (this.direction == directions.vertical) {
                var newTop = -index * obj._height;
                obj._$carousel.animate({
                        top: newTop + "px"
                    }, obj.duration, function() {
                        handleIndexChanged();
                    });
            } else {
                var newLeft = -index * obj._width;
                obj._$carousel.animate({
                        left: newLeft + "px"
                    }, obj.duration, function() {
                        handleIndexChanged();
                    });
            }
            
            function handleIndexChanged() {
                if (index == obj._currentIndex) {
                    return;
                }
                var evt = $.Event("onViewIndexChanged");
                evt.oldIndex = obj._currentIndex;
                evt.newIndex = index;
                obj._currentIndex = index;
                $(obj).trigger(evt);
            }
        },
        hasNext:function() {
            var count = obj._$carousel.children().length;
            return obj._currentIndex < count - 1;
        },
        goNext: function() {
            if (this.hasNext()) {
                this.gotoIndex(obj._currentIndex + 1);
            }
        },
        hasPrevious:function() {
            return obj._currentIndex > 0;
        },
        goPrevious: function() {
            if (this.hasPrevious()) {
                this.gotoIndex(obj._currentIndex - 1);
            }
        },
        refreshSize:function() {
            var obj = this;
            obj._height = obj._$wrapper.innerHeight();
            obj._width = obj._$wrapper.innerWidth();
            if (obj.direction == directions.horizontal) {
                obj._$carousel.width(obj._$carousel.children().length * obj._width);
                obj._$carousel.css("left", -obj._width * obj._currentIndex);
            }
            else{
                obj._$carousel.height(obj._$carousel.children().length * obj._height);
                obj._$carousel.css("top", -obj._height * obj._currentIndex);
            }
            obj._$carousel.children().width(obj._width).height(obj._height).addClass("carousel-view");
        },
        onViewIndexChanged:function(handler) {
            $(this).bind("onViewIndexChanged", function (args) {
                handler && handler(args.oldIndex, args.newIndex);
            });
        }
    };

})();
(function() {
    nova.widgets.busy = {
        show: function (content, deplay, css) {
            content = content == undefined ? 'Loading...' : content;
            var $busy = $('.busy');
            if ($busy.length > 0) {
                $busy.remove();
            }
            css = css != undefined && css != '' && css != null ? ' ' + css : '';
            var html = '<div class="busy' + css + '">\
                        <div class="busy-content">' + content + '</div>\
                    </div>';
            $busy = $(html).appendTo('#body');
            if (deplay == undefined || deplay == null || deplay <= 0) {
                showBusy();
            } else {
                setTimeout(function () {
                    showBusy();
                }, deplay);
            }

            function showBusy() {
                $busy.show();
                var $content = $('.busy-content');
                $content.css('top', ($busy.height() - $content.height()) / 2 + 'px');
                $content.css('left', ($busy.width() - $content.width()) / 2 + 'px');
            }
        },
        remove: function () {
            $('.busy').remove();
        }
    };
})();
(function() {
    nova.widgets.Dialog = function (id) {
        this.wrapper = "#body";
        this.id = id == undefined ? "dialog" + new Date().getTime() : id;
        this.modal = true;
        this.title = "";
        this.content = "";
        this.buttons = null;
        this.css = "";
        this.width = 0.8;
        this.height = 0.6;
        this.canClose = true;
        this.autoRemove = true;

        this.opened = null;
        this.closed = null;
        this.closing = null;
    };

    nova.widgets.Dialog.prototype = {
        show: function() {
            var dialog = this;

            var $dialog = $("#" + dialog.id);
            if ($dialog.length > 0) {
                $dialog.show();
                return;
            }

            $dialog = $("<div id='" + dialog.id + "' class='dialog'></div>");
            $dialog.appendTo($(dialog.wrapper));
            if (dialog.css != "") {
                $dialog.addClass(dialog.css);
            }
            if (dialog.modal) {
                $dialog.addClass("modal");
            }

            var $win = $("<div class='dialog-window'></div>");
            $win.appendTo($dialog);
            var winHeight = $(window).height();
            var winWidth = $(window).width();

            if (dialog.width <= 1) {
                $win.width(dialog.width * 100 + "%");
                $win.css("left", (1 - dialog.width) * 100 / 2 + "%");
            } else {
                $win.width(dialog.width + "px");
                $win.css("left", (winWidth - dialog.width) / 2 + "px");
            }
            if (dialog.height <= 1) {
                $win.height(dialog.height * 100 + "%");
                $win.css("top", (1 - 1 * dialog.height) * 100 / 2 + "%");
            } else {
                $win.height(dialog.height + "px");
                $win.css("top", (winHeight - dialog.height) / 2 + "px");
            }

            var $header = $("<div class='dialog-header'><div class='dialog-title'>" + dialog.title + "</div></div>");
            $header.appendTo($win);

            if (dialog.canClose) {
                var $close = $("<div class='dialog-close'><span></span></div>");
                $close.appendTo($header);
                nova.touch.bindClick($close, function() {
                    setTimeout(function() {
                        dialog.close();
                    }, 50);
                });
            }

            var $content = $("<div class='dialog-content'><div class='dialog-content-wrap'>" + dialog.content + "</div></div>");
            $content.appendTo($win);

            var $footer = null;
            if (dialog.buttons != null) {
                $footer = $("<div class='dialog-footer'><div class='dialog-footer-w1'><div class='dialog-footer-w2'></div></div></div>");
                $footer.appendTo($win);
                var $footerWrap = $footer.find(".dialog-footer-w2");
                for (p in dialog.buttons) {
                    var property = p;
                    var value = dialog.buttons[property];
                    if (typeof(value) == "function") {
                        var $button = $("<div class='btn' data-text='" + property + "'><span>" + property + "</span></div>");
                        $button.data("data", dialog);
                        $button.appendTo($footerWrap);
                        nova.touch.bindClick($button, function() {
                            try {
                                var text = $(this).attr("data-text");
                                dialog.buttons[text]();
                            } catch(err) {
                                alert("error: " + err);
                            }
                        });
                    }
                }
            }

            var contentHeight = $win.height() - $header.height();
            if ($footer != null) {
                contentHeight -= $footer.height();
            }
            $content.height(contentHeight + "px");
            if (dialog.opened != null) {
                dialog.opened();
            }
        },
        close: function() {
            var obj = this;
            var closingCancelled = false;
            if (obj.closing != null) {
                closingCancelled = obj.closing() == false;
            }
            if (closingCancelled) {
                return;
            }
            if (obj.autoRemove) {
                obj.remove();
            } else {
                $("#" + obj.id).hide();
            }
            if (obj.closed != null) {
                obj.closed();
            }
        },
        remove: function() {
            $("#" + this.id).remove();
        }
    };
})();

(function() {
    nova.widgets.ToastOption = function() {
        this.showDuration = 500;
        this.displayDuration = 2000;
        this.hideDuration = 500;
        this.css = "";
    };

    nova.widgets.toast = function (message, options) {
        if (options) {
            options = $.extend(new nova.widgets.ToastOption(), options);
        } else {
            options = new nova.widgets.ToastOption();
        }

        var $toast = $("<div class='toast'>" + message + "</div>");
        if (options.css != "") {
            $toast.addClass(options.css);
        }
        $toast.appendTo("#body");
        var centerX = $(window).width() / 2;
        var left = centerX - $toast.width() / 2;
        var bottom = $(window).height() / 10;
        $toast.css("bottom", 0 + "px");
        $toast.css("left", left + "px");
        $toast.animate({
            bottom: "+=" + bottom,
            opacity: 1
        }, {
            duration: options.showDuration,
            complete: function () {
                setTimeout(function () {
                    $toast.animate({
                        bottom: "-=35",
                        opacity: 0
                    }, {
                        duration: options.hideDuration,
                        complete: function () {
                            $toast.remove();
                        }
                    });
                }, options.displayDuration);
            }
        });
    };
})();


(function() {
    nova.widgets.BumpManager = function() {
        this.watchFrequency = 20;
        this.stopOnBumped = true;
        this.lastAcceleration = null;
        this.lastLastAcceleration = null;
        this.watchId = null;
    };
    
    nova.widgets.BumpManager.prototype = {
        start: function() {
            var obj = this;
            obj.lastAcceleration = null;
            obj.lastLastAcceleration = null;
            obj.watchId = navigator.accelerometer.watchAcceleration(function(acceleration) {
                if (obj.lastAcceleration == null) {
                    obj.lastAcceleration = acceleration;
                    return;
                }
                if (obj.lastAcceleration.timestamp == acceleration.timestamp) {
                    return;
                }
                if (obj.lastLastAcceleration != null) {
                    var dx1 = Math.abs(obj.lastAcceleration.x - obj.lastLastAcceleration.x);
                    var dy1 = Math.abs(obj.lastAcceleration.y - obj.lastLastAcceleration.y);
                    var dx2 = Math.abs(acceleration.x - obj.lastAcceleration.x);
                    var dy2 = Math.abs(acceleration.y - obj.lastAcceleration.y);
                    var dxx = Math.abs(acceleration.x - obj.lastLastAcceleration.x);
                    var dyy = Math.abs(acceleration.y - obj.lastLastAcceleration.y);
                    var bumped = ((dx1 + dx2) >= 10 && dxx < (dx1 + dx2) / 4)
                        || ((dy1 + dy2) >= 10 && dyy < (dy1 + dy2) / 4);
                    if (bumped) {
                        $(obj).trigger('onBumped');
                    }
                }
                obj.lastLastAcceleration = obj.lastAcceleration;
                obj.lastAcceleration = acceleration;
            },
                function() {
                    $(obj).trigger('onError');
                },
                {
                    frequency: obj.watchFrequency
                });
        },
        stop: function() {
            navigator.accelerometer.clearWatch(this.watchId);
        },
        onBumped: function(handler) {
            if (this.stopOnBumped) {
                this.stop();
            }
            $(this).bind('onBumped', handler);
        },
        onError: function(handler) {
            $(this).bind('onError', handler);
        }
    };
})();

(function() {
    nova.widgets.ShakeManager = function () {
        this.watchFrequency = 20;
        this.stopOnShaked = true;
        this.lastAcceleration = null;
        this.watchId = null;
        this.minShakeDistance = 17;
        this.minShakeGravity = 10;
        this.minShakedCount = 3;
        this._shakedCounter = 0;
    };

    nova.widgets.ShakeManager.prototype = {
        start: function() {
            var obj = this;
            obj.lastAcceleration = null;
            obj._shakedCounter = 0;
            obj.watchId = navigator.accelerometer.watchAcceleration(function(acceleration) {
                if (obj.lastAcceleration == null) {
                    obj.lastAcceleration = acceleration;
                    return;
                }
                if (obj.lastAcceleration.timestamp == acceleration.timestamp) {
                    return;
                }
                var dx = Math.abs(acceleration.x - obj.lastAcceleration.x);
                var dy = Math.abs(acceleration.y - obj.lastAcceleration.y);
                var dz = Math.abs(acceleration.z - obj.lastAcceleration.z);
                var shaked = dx > obj.minShakeDistance || dy > obj.minShakeDistance || dz > obj.minShakeGravity;
                if (shaked) {
                    obj._shakedCounter++;
                    if (obj._shakedCounter >= obj.minShakedCount) {
                        navigator.notification.vibrate(500);
                        obj._shakedCounter = 0;
                        if (obj.stopOnShaked) {
                            obj.stop();
                        }
                        $(obj).trigger('onShaked');
                    }
                }
                obj.lastAcceleration = acceleration;
            },
                function() {
                    $(obj).trigger('onError');
                },
                {
                    frequency: obj.watchFrequency
                });
        },
        stop: function() {
            navigator.accelerometer.clearWatch(this.watchId);
        },
        onShaked: function(handler) {
            $(this).bind('onShaked', handler);
        },
        onError: function(handler) {
            $(this).bind('onError', handler);
        }
    };
})();


(function() {
    nova.widgets.actionSheet = {
        _buttons: [],
        show: function (buttons, info) {
            var me = this;
            this._buttons = buttons;
            var $action = $('.actionsheet');
            if ($action.length > 0) {
                $action.remove();
            }
            var html = '<div class="actionsheet">\
                        <div class="actionsheet-info">' + (info == undefined ? '' : info) + '</div>\
                        <div class="actionsheet-buttons">\
                        </div>\
                    </div>';
            $action = $(html);
            $action.appendTo('#body');
            var $btns = $action.find('.actionsheet-buttons');
            for (var i = 0; i < buttons.length; i++) {
                var btn = buttons[i];
                var css = btn.css == undefined ? '' : ' ' + btn.css;
                var $btn = $('<div class="actionsheet-button' + css + '">' + btn.text + '</div>');
                $btn.appendTo($btns);
                nova.touch.bindClick($btn, function () {
                    me._buttons[$(this).prevAll().length].handler();
                });
            }
            nova.touch.bindClick('.actionsheet', function () {
                $action.remove();
                nova.application.currentPage.backbuttonHandlers.pop();
            });
            if (nova.application.currentPage.isBackbuttonDisabled == false) {
                nova.application.currentPage.backbutton(function () {
                    $action.remove();
                });
            }
        },
        remove: function () {
            $('.actionsheet').remove();
        },
        createButton: function (text, css, handler) {
            return {
                text: text,
                css: css,
                handler: handler
            };
        },
        isShowing: function () {
            return $('.actionsheet').length > 0;
        }
    };
})();
(function() {
    var isIDevice = (/iphone|ipad/gi).test(navigator.appVersion);
    nova.widgets.BeepPlayer = function(src) {
        this.src = "";
        if (src) {
            this.src = $.trim(src);
        }
    };

    nova.widgets.BeepPlayer.prototype = {
        play: function(src) {
            var me = this;
            if (src) {
                me.src = $.trim(src);
            }
            if (me.src == "" || me.src == null) {
                navigator.notification.beep(2);
                return;
            }
            me._beep();
        },
        _beep: function() {
            var me = this;
            if (isIDevice || /Chrome\/\d/.test(navigator.userAgent)) {
                var $audio = $("#beepaudio");
                if (!$audio || $audio.length <= 0) {
                    $audio = $('<audio id="beepaudio" style="display: none"></audio>');
                    $("#content").append($audio);
                }
                var media = $audio[0];
                media.src = encodeURI(me.src);
                media.load();
                media.play();
            } else {
                var media = new Media(encodeURI(me.src), function() {
                    console.log("Audio Success.");
                }, function(err) {
                    console.log("Audio Error: " + err + ".");
                }, function(status) {
                    if (status == Media.MEDIA_STOPPED) {
                        media.release();
                    }
                });
                media.play();
            }
        }
    };
})();
(function () {
    nova.widgets.Logger = function (millisecond, showLog) {
        this.time = 5000;
        this.showLog = false;
        if (millisecond != null && (millisecond * 1) > 0) {
            this.time = millisecond;
        }
        if(showLog!=null) {
            this.showLog = showLog;
        }

    };

    nova.widgets.Logger.prototype.log = function (message) {
        if (this.showLog == false) {
            return;
        }
        var $ul = $('ul#log');
        if ($ul.length == 0) {
            $ul = $('<ul id="log"></ul>');
            $ul.appendTo('#layout');
        }
        var $li = $('<li>' + message + '</li>');
        $li.appendTo($ul);
        $li.click(function () {
            $li.remove();
        });
        setTimeout(function () {
            $li.remove();
        }, this.time);
    };
})();